/*
* Copyright (C)
* Copyright (C)
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/

#include "Common.h"
#include "ObjectMgr.h"
#include "Player.h"
#include "World.h"
#include "WorldPacket.h"
#include "WorldSession.h"
#include "DatabaseEnv.h"
#include "DBCStores.h"
#include "ScriptMgr.h"
#include "AccountMgr.h"
#include "AuctionHouseMgr.h"
#include "Item.h"
#include "Language.h"
#include "Logging/Log.h"
#include <vector>
#include "AvgDiffTracker.h"
#include "AsyncAuctionListing.h"

enum eAuctionHouse
{
	AH_MINIMUM_DEPOSIT = 100,
};

AuctionHouseMgr::AuctionHouseMgr()
{
}

AuctionHouseMgr::~AuctionHouseMgr()
{
	for (ItemMap::iterator itr = mAitems.begin(); itr != mAitems.end(); ++itr)
		delete itr->second;
}

AuctionHouseObject* AuctionHouseMgr::GetAuctionsMap(uint32 factionTemplateId)
{
	if (sWorld->getBoolConfig(CONFIG_ALLOW_TWO_SIDE_INTERACTION_AUCTION))
		return &mNeutralAuctions;

	// team have linked auction houses
	FactionTemplateEntry const* u_entry = sFactionTemplateStore.LookupEntry(factionTemplateId);
	if (!u_entry)
		return &mNeutralAuctions;
	else if (u_entry->ourMask & FACTION_MASK_ALLIANCE)
		return &mAllianceAuctions;
	else if (u_entry->ourMask & FACTION_MASK_HORDE)
		return &mHordeAuctions;
	else
		return &mNeutralAuctions;
}

uint32 AuctionHouseMgr::GetAuctionDeposit(AuctionHouseEntry const* entry, uint32 time, Item* pItem, uint32 count)
{
	uint32 MSV = pItem->GetTemplate()->SellPrice;

	if (MSV <= 0)
		return AH_MINIMUM_DEPOSIT;

	float multiplier = CalculatePct(float(entry->depositPercent), 3);
	uint32 timeHr = (((time / 60) / 60) / 12);
	uint32 deposit = uint32(((multiplier * MSV * count / 3) * timeHr * 3) * sWorld->getRate(RATE_AUCTION_DEPOSIT));

	;//sLog->outDebug(LOG_FILTER_AUCTIONHOUSE, "MSV:        %u", MSV);
	;//sLog->outDebug(LOG_FILTER_AUCTIONHOUSE, "Items:      %u", count);
	;//sLog->outDebug(LOG_FILTER_AUCTIONHOUSE, "Multiplier: %f", multiplier);
	;//sLog->outDebug(LOG_FILTER_AUCTIONHOUSE, "Deposit:    %u", deposit);

	if (deposit < AH_MINIMUM_DEPOSIT)
		return AH_MINIMUM_DEPOSIT;
	else
		return deposit;
}

//does not clear ram
void AuctionHouseMgr::SendAuctionWonMail(AuctionEntry* auction, SQLTransaction& trans)
{
	Item* pItem = GetAItem(auction->item_guidlow);
	if (!pItem)
		return;

	uint64 bidder_guid = MAKE_NEW_GUID(auction->bidder, 0, HIGHGUID_PLAYER);
	uint32 bidder_accId = 0;
	Player* bidder = ObjectAccessor::FindPlayerInOrOutOfWorld(bidder_guid);
	if (bidder)
		bidder_accId = bidder->GetSession()->GetAccountId();
	else
		bidder_accId = sObjectMgr->GetPlayerAccountIdByGUID(bidder_guid);

	// receiver exist
	if (bidder || bidder_accId)
	{
		// set owner to bidder (to prevent delete item with sender char deleting)
		// owner in `data` will set at mail receive and item extracting
		PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_ITEM_OWNER);
		stmt->setUInt32(0, auction->bidder);
		stmt->setUInt32(1, pItem->GetGUIDLow());
		trans->Append(stmt);

		if (bidder)
		{
			bidder->GetSession()->SendAuctionBidderNotification(auction->GetHouseId(), auction->Id, bidder_guid, 0, 0, auction->item_template);
			// FIXME: for offline player need also
			bidder->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_WON_AUCTIONS, 1);
		}

		MailDraft(auction->BuildAuctionMailSubject(AUCTION_WON), AuctionEntry::BuildAuctionMailBody(auction->owner, auction->bid, auction->buyout, 0, 0))
			.AddItem(pItem)
			.SendMailTo(trans, MailReceiver(bidder, auction->bidder), auction, MAIL_CHECK_MASK_COPIED);
	}
	else
		sAuctionMgr->RemoveAItem(auction->item_guidlow, true);
}

void AuctionHouseMgr::SendAuctionSalePendingMail(AuctionEntry* auction, SQLTransaction& trans)
{
	uint64 owner_guid = MAKE_NEW_GUID(auction->owner, 0, HIGHGUID_PLAYER);
	Player* owner = ObjectAccessor::FindPlayerInOrOutOfWorld(owner_guid);
	uint32 owner_accId = sObjectMgr->GetPlayerAccountIdByGUID(owner_guid);
	// owner exist (online or offline)
	if (owner || owner_accId)
		MailDraft(auction->BuildAuctionMailSubject(AUCTION_SALE_PENDING), AuctionEntry::BuildAuctionMailBody(auction->bidder, auction->bid, auction->buyout, auction->deposit, auction->GetAuctionCut()))
		.SendMailTo(trans, MailReceiver(owner, auction->owner), auction, MAIL_CHECK_MASK_COPIED);
}

//call this method to send mail to auction owner, when auction is successful, it does not clear ram
void AuctionHouseMgr::SendAuctionSuccessfulMail(AuctionEntry* auction, SQLTransaction& trans)
{
	uint64 owner_guid = MAKE_NEW_GUID(auction->owner, 0, HIGHGUID_PLAYER);
	Player* owner = ObjectAccessor::FindPlayerInOrOutOfWorld(owner_guid);
	uint32 owner_accId = sObjectMgr->GetPlayerAccountIdByGUID(owner_guid);
	// owner exist
	if (owner || owner_accId)
	{
		uint32 profit = auction->bid + auction->deposit - auction->GetAuctionCut();

		if (owner)
		{
			owner->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_GOLD_EARNED_BY_AUCTIONS, profit);
			owner->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_HIGHEST_AUCTION_SOLD, auction->bid);
			owner->GetSession()->SendAuctionOwnerNotification(auction);
		}

		MailDraft(auction->BuildAuctionMailSubject(AUCTION_SUCCESSFUL), AuctionEntry::BuildAuctionMailBody(auction->bidder, auction->bid, auction->buyout, auction->deposit, auction->GetAuctionCut()))
			.AddMoney(profit)
			.SendMailTo(trans, MailReceiver(owner, auction->owner), auction, MAIL_CHECK_MASK_COPIED, sWorld->getIntConfig(CONFIG_MAIL_DELIVERY_DELAY));

		if (auction->bid >= 500 * GOLD)
		if (const GlobalPlayerData* gpd = sWorld->GetGlobalPlayerData(auction->bidder))
		{
			uint64 bidder_guid = MAKE_NEW_GUID(auction->bidder, 0, HIGHGUID_PLAYER);
			Player* bidder = ObjectAccessor::FindPlayerInOrOutOfWorld(bidder_guid);
			std::string owner_name = "";
			uint8 owner_level = 0;
			if (const GlobalPlayerData* gpd_owner = sWorld->GetGlobalPlayerData(auction->owner))
			{
				owner_name = gpd_owner->name;
				owner_level = gpd_owner->level;
			}
			CharacterDatabase.PExecute("INSERT INTO log_money VALUES(%u, %u, \"%s\", \"%s\", %u, \"%s\", %u, \"<AH> profit: %ug, bidder: %s %u lvl (guid: %u), seller: %s %u lvl (guid: %u), item %u (%u)\", NOW())", gpd->accountId, auction->bidder, gpd->name.c_str(), bidder ? bidder->GetSession()->GetRemoteAddress().c_str() : "", owner_accId, owner_name.c_str(), auction->bid, (profit / GOLD), gpd->name.c_str(), gpd->level, auction->bidder, owner_name.c_str(), owner_level, auction->owner, auction->item_template, auction->itemCount);
		}
	}
}

//does not clear ram
void AuctionHouseMgr::SendAuctionExpiredMail(AuctionEntry* auction, SQLTransaction& trans)
{
	//return an item in auction to its owner by mail
	Item* pItem = GetAItem(auction->item_guidlow);
	if (!pItem)
		return;

	uint64 owner_guid = MAKE_NEW_GUID(auction->owner, 0, HIGHGUID_PLAYER);
	Player* owner = ObjectAccessor::FindPlayerInOrOutOfWorld(owner_guid);
	uint32 owner_accId = sObjectMgr->GetPlayerAccountIdByGUID(owner_guid);

	// owner exist
	if (owner || owner_accId)
	{
		if (owner)
			owner->GetSession()->SendAuctionOwnerNotification(auction);

		MailDraft(auction->BuildAuctionMailSubject(AUCTION_EXPIRED), AuctionEntry::BuildAuctionMailBody(0, 0, auction->buyout, auction->deposit, 0))
			.AddItem(pItem)
			.SendMailTo(trans, MailReceiver(owner, auction->owner), auction, MAIL_CHECK_MASK_COPIED, 0);
	}
	else
		sAuctionMgr->RemoveAItem(auction->item_guidlow, true);
}

//this function sends mail to old bidder
void AuctionHouseMgr::SendAuctionOutbiddedMail(AuctionEntry* auction, uint32 newPrice, Player* newBidder, SQLTransaction& trans)
{
	uint64 oldBidder_guid = MAKE_NEW_GUID(auction->bidder, 0, HIGHGUID_PLAYER);
	Player* oldBidder = ObjectAccessor::FindPlayerInOrOutOfWorld(oldBidder_guid);

	uint32 oldBidder_accId = 0;
	if (!oldBidder)
		oldBidder_accId = sObjectMgr->GetPlayerAccountIdByGUID(oldBidder_guid);

	// old bidder exist
	if (oldBidder || oldBidder_accId)
	{
		if (oldBidder && newBidder)
			oldBidder->GetSession()->SendAuctionBidderNotification(auction->GetHouseId(), auction->Id, newBidder->GetGUID(), newPrice, auction->GetAuctionOutBid(), auction->item_template);

		MailDraft(auction->BuildAuctionMailSubject(AUCTION_OUTBIDDED), AuctionEntry::BuildAuctionMailBody(auction->owner, auction->bid, auction->buyout, auction->deposit, auction->GetAuctionCut()))
			.AddMoney(auction->bid)
			.SendMailTo(trans, MailReceiver(oldBidder, auction->bidder), auction, MAIL_CHECK_MASK_COPIED);
	}
}

//this function sends mail, when auction is cancelled to old bidder
void AuctionHouseMgr::SendAuctionCancelledToBidderMail(AuctionEntry* auction, SQLTransaction& trans)
{
	uint64 bidder_guid = MAKE_NEW_GUID(auction->bidder, 0, HIGHGUID_PLAYER);
	Player* bidder = ObjectAccessor::FindPlayerInOrOutOfWorld(bidder_guid);

	uint32 bidder_accId = 0;
	if (!bidder)
		bidder_accId = sObjectMgr->GetPlayerAccountIdByGUID(bidder_guid);

	// bidder exist
	if (bidder || bidder_accId)
		MailDraft(auction->BuildAuctionMailSubject(AUCTION_CANCELLED_TO_BIDDER), AuctionEntry::BuildAuctionMailBody(auction->owner, auction->bid, auction->buyout, auction->deposit, 0))
		.AddMoney(auction->bid)
		.SendMailTo(trans, MailReceiver(bidder, auction->bidder), auction, MAIL_CHECK_MASK_COPIED);
}

void AuctionHouseMgr::LoadAuctionItems()
{
	uint32 oldMSTime = getMSTime();

	// need to clear in case we are reloading
	if (!mAitems.empty())
	{
		for (ItemMap::iterator itr = mAitems.begin(); itr != mAitems.end(); ++itr)
			delete itr->second;

		mAitems.clear();
	}

	// data needs to be at first place for Item::LoadFromDB
	PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_AUCTION_ITEMS);
	PreparedQueryResult result = CharacterDatabase.Query(stmt);

	if (!result)
	{
		sLog->outString(">> Loaded 0 auction items. DB table `auctionhouse` or `item_instance` is empty!");
		sLog->outString();
		return;
	}

	uint32 count = 0;

	do
	{

		Field* fields = result->Fetch();

		uint32 item_guid = fields[11].GetUInt32();
		uint32 item_template = fields[12].GetUInt32();

		ItemTemplate const* proto = sObjectMgr->GetItemTemplate(item_template);
		if (!proto)
		{
			sLog->outError("AuctionHouseMgr::LoadAuctionItems: Unknown item (GUID: %u id: #%u) in auction, skipped.", item_guid, item_template);
			continue;
		}

		Item* item = NewItemOrBag(proto);
		if (!item->LoadFromDB(item_guid, 0, fields, item_template))
		{
			delete item;
			continue;
		}
		AddAItem(item);

		++count;
	} while (result->NextRow());

	sLog->outString(">> Loaded %u auction items in %u ms", count, GetMSTimeDiffToNow(oldMSTime));
	sLog->outString();
}

void AuctionHouseMgr::LoadAuctions()
{
	uint32 oldMSTime = getMSTime();

	PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_AUCTIONS);
	PreparedQueryResult result = CharacterDatabase.Query(stmt);

	if (!result)
	{
		sLog->outString(">> Loaded 0 auctions. DB table `auctionhouse` is empty.");
		sLog->outString();
		return;
	}

	uint32 count = 0;

	SQLTransaction trans = CharacterDatabase.BeginTransaction();
	do
	{
		Field* fields = result->Fetch();

		AuctionEntry* aItem = new AuctionEntry();
		if (!aItem->LoadFromDB(fields))
		{
			aItem->DeleteFromDB(trans);
			delete aItem;
			continue;
		}

		GetAuctionsMap(aItem->factionTemplateId)->AddAuction(aItem);
		count++;
	} while (result->NextRow());

	CharacterDatabase.CommitTransaction(trans);

	sLog->outString(">> Loaded %u auctions in %u ms", count, GetMSTimeDiffToNow(oldMSTime));
	sLog->outString();
}

void AuctionHouseMgr::AddAItem(Item* it)
{
	ASSERT(it);
	ASSERT(mAitems.find(it->GetGUIDLow()) == mAitems.end());
	mAitems[it->GetGUIDLow()] = it;
}

bool AuctionHouseMgr::RemoveAItem(uint32 id, bool deleteFromDB)
{
	ItemMap::iterator i = mAitems.find(id);
	if (i == mAitems.end())
		return false;

	if (deleteFromDB)
	{
		SQLTransaction trans = CharacterDatabase.BeginTransaction();
		i->second->FSetState(ITEM_REMOVED);
		i->second->SaveToDB(trans);
		CharacterDatabase.CommitTransaction(trans);
	}

	mAitems.erase(i);
	return true;
}

void AuctionHouseMgr::Update()
{
	mHordeAuctions.Update();
	mAllianceAuctions.Update();
	mNeutralAuctions.Update();
}

AuctionHouseEntry const* AuctionHouseMgr::GetAuctionHouseEntry(uint32 factionTemplateId)
{
	uint32 houseid = 7; // goblin auction house

	if (!sWorld->getBoolConfig(CONFIG_ALLOW_TWO_SIDE_INTERACTION_AUCTION))
	{
		//FIXME: found way for proper auctionhouse selection by another way
		// AuctionHouse.dbc have faction field with _player_ factions associated with auction house races.
		// but no easy way convert creature faction to player race faction for specific city
		switch (factionTemplateId)
		{
		case   12: houseid = 1; break; // human
		case   29: houseid = 6; break; // orc, and generic for horde
		case   55: houseid = 2; break; // dwarf, and generic for alliance
		case   68: houseid = 4; break; // undead
		case   80: houseid = 3; break; // n-elf
		case  104: houseid = 5; break; // trolls
		case  120: houseid = 7; break; // booty bay, neutral
		case  474: houseid = 7; break; // gadgetzan, neutral
		case  855: houseid = 7; break; // everlook, neutral
		case 1604: houseid = 6; break; // b-elfs,
		default:                       // for unknown case
		{
										   FactionTemplateEntry const* u_entry = sFactionTemplateStore.LookupEntry(factionTemplateId);
										   if (!u_entry)
											   houseid = 7; // goblin auction house
										   else if (u_entry->ourMask & FACTION_MASK_ALLIANCE)
											   houseid = 1; // human auction house
										   else if (u_entry->ourMask & FACTION_MASK_HORDE)
											   houseid = 6; // orc auction house
										   else
											   houseid = 7; // goblin auction house
										   break;
		}
		}
	}

	return sAuctionHouseStore.LookupEntry(houseid);
}

void AuctionHouseObject::AddAuction(AuctionEntry* auction)
{
	ASSERT(auction);

	AuctionsMap[auction->Id] = auction;
	sScriptMgr->OnAuctionAdd(this, auction);
}

bool AuctionHouseObject::RemoveAuction(AuctionEntry* auction)
{
	bool wasInMap = AuctionsMap.erase(auction->Id) ? true : false;

	sScriptMgr->OnAuctionRemove(this, auction);

	// we need to delete the entry, it is not referenced any more
	delete auction;
	auction = NULL;

	return wasInMap;
}

void AuctionHouseObject::Update()
{
	time_t checkTime = sWorld->GetGameTime() + 60;
	///- Handle expired auctions

	// If storage is empty, no need to update. next == NULL in this case.
	if (AuctionsMap.empty())
		return;

	SQLTransaction trans = CharacterDatabase.BeginTransaction();

	for (AuctionEntryMap::iterator itr, iter = AuctionsMap.begin(); iter != AuctionsMap.end();)
	{
		itr = iter++;
		AuctionEntry* auction = (*itr).second;

		if (auction->expire_time > checkTime)
			continue;

		///- Either cancel the auction if there was no bidder
		if (auction->bidder == 0)
		{
			sAuctionMgr->SendAuctionExpiredMail(auction, trans);
			sScriptMgr->OnAuctionExpire(this, auction);
		}
		///- Or perform the transaction
		else
		{
			//we should send an "item sold" message if the seller is online
			//we send the item to the winner
			//we send the money to the seller
			sAuctionMgr->SendAuctionSuccessfulMail(auction, trans);
			sAuctionMgr->SendAuctionWonMail(auction, trans);
			sScriptMgr->OnAuctionSuccessful(this, auction);
		}

		///- In any case clear the auction
		auction->DeleteFromDB(trans);

		sAuctionMgr->RemoveAItem(auction->item_guidlow);
		RemoveAuction(auction);
	}
	CharacterDatabase.CommitTransaction(trans);
}

void AuctionHouseObject::BuildListBidderItems(WorldPacket& data, Player* player, uint32& count, uint32& totalcount)
{
	for (AuctionEntryMap::const_iterator itr = AuctionsMap.begin(); itr != AuctionsMap.end(); ++itr)
	{
		AuctionEntry* Aentry = itr->second;
		if (Aentry && Aentry->bidder == player->GetGUIDLow())
		{
			if (itr->second->BuildAuctionInfo(data))
				++count;

			++totalcount;
		}
	}
}

void AuctionHouseObject::BuildListOwnerItems(WorldPacket& data, Player* player, uint32& count, uint32& totalcount)
{
	for (AuctionEntryMap::const_iterator itr = AuctionsMap.begin(); itr != AuctionsMap.end(); ++itr)
	{
		AuctionEntry* Aentry = itr->second;
		if (Aentry && Aentry->owner == player->GetGUIDLow())
		{
			if (Aentry->BuildAuctionInfo(data))
				++count;

			++totalcount;
		}
	}
}

bool AuctionHouseObject::BuildListAuctionItems(WorldPacket& data, Player* player,
	std::wstring const& wsearchedname, uint32 listfrom, uint8 levelmin, uint8 levelmax, uint8 usable,
	uint32 inventoryType, uint32 itemClass, uint32 itemSubClass, uint32 quality,
	uint32& count, uint32& totalcount, uint8 getAll)
{
	uint32 itrcounter = 0;

	// pussywizard: optimization, this is a simplified case
	if (itemClass == 0xffffffff && itemSubClass == 0xffffffff && inventoryType == 0xffffffff && quality == 0xffffffff && levelmin == 0x00 && levelmax == 0x00 && usable == 0x00 && wsearchedname.empty())
	{
		totalcount = Getcount();
		if (listfrom < totalcount)
		{
			AuctionEntryMap::iterator itr = AuctionsMap.begin();
			std::advance(itr, listfrom);
			for (; itr != AuctionsMap.end(); ++itr)
			{
				itr->second->BuildAuctionInfo(data);
				if ((++count) >= 50)
					break;
			}
		}
		return true;
	}

	time_t curTime = sWorld->GetGameTime();

	for (AuctionEntryMap::const_iterator itr = AuctionsMap.begin(); itr != AuctionsMap.end(); ++itr)
	{
		if (AsyncAuctionListingMgr::IsAuctionListingAllowed() == false) // pussywizard: World::Update is waiting for us...
		if ((itrcounter++) % 100 == 0) // check condition every 100 iterations
		if (avgDiffTracker.getAverage() >= 30 || getMSTimeDiff(World::GetGameTimeMS(), getMSTime()) >= 10) // pussywizard: stop immediately if diff is high or waiting too long
			return false;

		AuctionEntry* Aentry = itr->second;
		// Skip expired auctions
		if (Aentry->expire_time < curTime)
			continue;

		Item* item = sAuctionMgr->GetAItem(Aentry->item_guidlow);
		if (!item)
			continue;

		ItemTemplate const* proto = item->GetTemplate();

		if (itemClass != 0xffffffff && proto->Class != itemClass)
			continue;

		if (itemSubClass != 0xffffffff && proto->SubClass != itemSubClass)
			continue;

		if (inventoryType != 0xffffffff && proto->InventoryType != inventoryType)
		{
			// xinef: exception, robes are counted as chests
			if (inventoryType != INVTYPE_CHEST || proto->InventoryType != INVTYPE_ROBE)
				continue;
		}

		if (quality != 0xffffffff && proto->Quality != quality)
			continue;

		if (levelmin != 0x00 && (proto->RequiredLevel < levelmin || (levelmax != 0x00 && proto->RequiredLevel > levelmax)))
			continue;

		if (usable != 0x00)
		{
			if (player->CanUseItem(item) != EQUIP_ERR_OK)
				continue;

			// xinef: check already learded recipes and pets
			if (proto->Spells[1].SpellTrigger == ITEM_SPELLTRIGGER_LEARN_SPELL_ID && player->HasSpell(proto->Spells[1].SpellId))
				continue;
		}

		// Allow search by suffix (ie: of the Monkey) or partial name (ie: Monkey)
		// No need to do any of this if no search term was entered
		if (!wsearchedname.empty())
		{
			std::string name = proto->Name1;
			if (name.empty())
				continue;

			// DO NOT use GetItemEnchantMod(proto->RandomProperty) as it may return a result
			//  that matches the search but it may not equal item->GetItemRandomPropertyId()
			//  used in BuildAuctionInfo() which then causes wrong items to be listed
			int32 propRefID = item->GetItemRandomPropertyId();

			if (propRefID)
			{
				// Append the suffix to the name (ie: of the Monkey) if one exists
				// These are found in ItemRandomSuffix.dbc and ItemRandomProperties.dbc
				//  even though the DBC name seems misleading

				char* const* suffix = NULL;

				if (propRefID < 0)
				{
					const ItemRandomSuffixEntry* itemRandEntry = sItemRandomSuffixStore.LookupEntry(-item->GetItemRandomPropertyId());
					if (itemRandEntry)
						suffix = itemRandEntry->nameSuffix;
				}
				else
				{
					const ItemRandomPropertiesEntry* itemRandEntry = sItemRandomPropertiesStore.LookupEntry(item->GetItemRandomPropertyId());
					if (itemRandEntry)
						suffix = itemRandEntry->nameSuffix;
				}

				// dbc local name
				if (suffix)
				{
					// Append the suffix (ie: of the Monkey) to the name using localization
					// or default enUS if localization is invalid
					name += ' ';
					name += suffix[LOCALE_enUS];
				}
			}

			// Perform the search (with or without suffix)
			if (!Utf8FitTo(name, wsearchedname))
				continue;
		}

		// Add the item if no search term or if entered search term was found
		if (count < 50 && totalcount >= listfrom)
		{
			++count;
			Aentry->BuildAuctionInfo(data);
		}
		++totalcount;
	}

	return true;
}

//this function inserts to WorldPacket auction's data
bool AuctionEntry::BuildAuctionInfo(WorldPacket& data) const
{
	Item* item = sAuctionMgr->GetAItem(item_guidlow);
	if (!item)
	{
		sLog->outError("AuctionEntry::BuildAuctionInfo: Auction %u has a non-existent item: %u", Id, item_guidlow);
		return false;
	}
	data << uint32(Id);
	data << uint32(item->GetEntry());

	for (uint8 i = 0; i < MAX_INSPECTED_ENCHANTMENT_SLOT; ++i)
	{
		data << uint32(item->GetEnchantmentId(EnchantmentSlot(i)));
		data << uint32(item->GetEnchantmentDuration(EnchantmentSlot(i)));
		data << uint32(item->GetEnchantmentCharges(EnchantmentSlot(i)));
	}

	data << int32(item->GetItemRandomPropertyId());                 // Random item property id
	data << uint32(item->GetItemSuffixFactor());                    // SuffixFactor
	data << uint32(item->GetCount());                               // item->count
	data << uint32(item->GetSpellCharges());                        // item->charge FFFFFFF
	data << uint32(0);                                              // Unknown
	data << uint64(owner);                                          // Auction->owner
	data << uint32(startbid);                                       // Auction->startbid (not sure if useful)
	data << uint32(bid ? GetAuctionOutBid() : 0);
	// Minimal outbid
	data << uint32(buyout);                                         // Auction->buyout
	data << uint32((expire_time - time(NULL)) * IN_MILLISECONDS);   // time left
	data << uint64(bidder);                                         // auction->bidder current
	data << uint32(bid);                                            // current bid
	return true;
}

uint32 AuctionEntry::GetAuctionCut() const
{
	int32 cut = int32(CalculatePct(bid, auctionHouseEntry->cutPercent) * sWorld->getRate(RATE_AUCTION_CUT));
	return std::max(cut, 0);
}

/// the sum of outbid is (1% from current bid)*5, if bid is very small, it is 1c
uint32 AuctionEntry::GetAuctionOutBid() const
{
	uint32 outbid = CalculatePct(bid, 5);
	return outbid ? outbid : 1;
}

void AuctionEntry::DeleteFromDB(SQLTransaction& trans) const
{
	PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_AUCTION);
	stmt->setUInt32(0, Id);
	trans->Append(stmt);
}

void AuctionEntry::SaveToDB(SQLTransaction& trans) const
{
	PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_AUCTION);
	stmt->setUInt32(0, Id);
	stmt->setUInt32(1, auctioneer);
	stmt->setUInt32(2, item_guidlow);
	stmt->setUInt32(3, owner);
	stmt->setUInt32(4, buyout);
	stmt->setUInt32(5, uint32(expire_time));
	stmt->setUInt32(6, bidder);
	stmt->setUInt32(7, bid);
	stmt->setUInt32(8, startbid);
	stmt->setUInt32(9, deposit);
	trans->Append(stmt);
}

bool AuctionEntry::LoadFromDB(Field* fields)
{
	Id = fields[0].GetUInt32();
	auctioneer = fields[1].GetUInt32();
	item_guidlow = fields[2].GetUInt32();
	item_template = fields[3].GetUInt32();
	itemCount = fields[4].GetUInt32();
	owner = fields[5].GetUInt32();
	buyout = fields[6].GetUInt32();
	expire_time = fields[7].GetUInt32();
	bidder = fields[8].GetUInt32();
	bid = fields[9].GetUInt32();
	startbid = fields[10].GetUInt32();
	deposit = fields[11].GetUInt32();

	CreatureData const* auctioneerData = sObjectMgr->GetCreatureData(auctioneer);
	if (!auctioneerData)
	{
		sLog->outError("Auction %u has not a existing auctioneer (GUID : %u)", Id, auctioneer);
		return false;
	}

	CreatureTemplate const* auctioneerInfo = sObjectMgr->GetCreatureTemplate(auctioneerData->id);
	if (!auctioneerInfo)
	{
		sLog->outError("Auction %u has not a existing auctioneer (GUID : %u Entry: %u)", Id, auctioneer, auctioneerData->id);
		return false;
	}

	factionTemplateId = auctioneerInfo->faction;
	auctionHouseEntry = AuctionHouseMgr::GetAuctionHouseEntry(factionTemplateId);
	if (!auctionHouseEntry)
	{
		sLog->outError("Auction %u has auctioneer (GUID : %u Entry: %u) with wrong faction %u", Id, auctioneer, auctioneerData->id, factionTemplateId);
		return false;
	}

	// check if sold item exists for guid
	// and item_template in fact (GetAItem will fail if problematic in result check in AuctionHouseMgr::LoadAuctionItems)
	if (!sAuctionMgr->GetAItem(item_guidlow))
	{
		sLog->outError("Auction %u has not a existing item : %u", Id, item_guidlow);
		return false;
	}
	return true;
}

bool AuctionEntry::LoadFromFieldList(Field* fields)
{
	// Loads an AuctionEntry item from a field list. Unlike "LoadFromDB()", this one
	//  does not require the AuctionEntryMap to have been loaded with items. It simply
	//  acts as a wrapper to fill out an AuctionEntry struct from a field list

	Id = fields[0].GetUInt32();
	auctioneer = fields[1].GetUInt32();
	item_guidlow = fields[2].GetUInt32();
	item_template = fields[3].GetUInt32();
	itemCount = fields[4].GetUInt32();
	owner = fields[5].GetUInt32();
	buyout = fields[6].GetUInt32();
	expire_time = fields[7].GetUInt32();
	bidder = fields[8].GetUInt32();
	bid = fields[9].GetUInt32();
	startbid = fields[10].GetUInt32();
	deposit = fields[11].GetUInt32();

	CreatureData const* auctioneerData = sObjectMgr->GetCreatureData(auctioneer);
	if (!auctioneerData)
	{
		sLog->outError("AuctionEntry::LoadFromFieldList() - Auction %u has not a existing auctioneer (GUID : %u)", Id, auctioneer);
		return false;
	}

	CreatureTemplate const* auctioneerInfo = sObjectMgr->GetCreatureTemplate(auctioneerData->id);
	if (!auctioneerInfo)
	{
		sLog->outError("AuctionEntry::LoadFromFieldList() - Auction %u has not a existing auctioneer (GUID : %u Entry: %u)", Id, auctioneer, auctioneerData->id);
		return false;
	}

	factionTemplateId = auctioneerInfo->faction;
	auctionHouseEntry = AuctionHouseMgr::GetAuctionHouseEntry(factionTemplateId);

	if (!auctionHouseEntry)
	{
		sLog->outError("AuctionEntry::LoadFromFieldList() - Auction %u has auctioneer (GUID : %u Entry: %u) with wrong faction %u", Id, auctioneer, auctioneerData->id, factionTemplateId);
		return false;
	}

	return true;
}

std::string AuctionEntry::BuildAuctionMailSubject(MailAuctionAnswers response) const
{
	std::ostringstream strm;
	strm << item_template << ":0:" << response << ':' << Id << ':' << itemCount;
	return strm.str();
}

std::string AuctionEntry::BuildAuctionMailBody(uint32 lowGuid, uint32 bid, uint32 buyout, uint32 deposit, uint32 cut)
{
	std::ostringstream strm;
	strm.width(16);
	strm << std::right << std::hex << MAKE_NEW_GUID(lowGuid, 0, HIGHGUID_PLAYER);   // HIGHGUID_PLAYER always present, even for empty guids
	strm << std::dec << ':' << bid << ':' << buyout;
	strm << ':' << deposit << ':' << cut;
	return strm.str();
}